1   /*
2    * Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package com.sun.jmx.mbeanserver;
27  
28  import static com.sun.jmx.mbeanserver.Util.*;
29  import static com.sun.jmx.mbeanserver.MXBeanIntrospector.typeName;
30  
31  import static javax.management.openmbean.SimpleType.*;
32  
33  import com.sun.jmx.remote.util.EnvHelp;
34  
35  import java.beans.ConstructorProperties;
36  import java.io.InvalidObjectException;
37  import java.lang.annotation.ElementType;
38  import java.lang.ref.WeakReference;
39  import java.lang.reflect.Array;
40  import java.lang.reflect.Constructor;
41  import java.lang.reflect.Field;
42  import java.lang.reflect.GenericArrayType;
43  import java.lang.reflect.Method;
44  import java.lang.reflect.Modifier;
45  import java.lang.reflect.ParameterizedType;
46  import java.lang.reflect.Proxy;
47  import java.lang.reflect.Type;
48  import java.util.ArrayList;
49  import java.util.Arrays;
50  import java.util.BitSet;
51  import java.util.Collection;
52  import java.util.Comparator;
53  import java.util.HashSet;
54  import java.util.List;
55  import java.util.Map;
56  import java.util.Set;
57  import java.util.SortedMap;
58  import java.util.SortedSet;
59  import java.util.TreeSet;
60  import java.util.WeakHashMap;
61  
62  import javax.management.JMX;
63  import javax.management.ObjectName;
64  import javax.management.openmbean.ArrayType;
65  import javax.management.openmbean.CompositeData;
66  import javax.management.openmbean.CompositeDataInvocationHandler;
67  import javax.management.openmbean.CompositeDataSupport;
68  import javax.management.openmbean.CompositeDataView;
69  import javax.management.openmbean.CompositeType;
70  import javax.management.openmbean.OpenDataException;
71  import javax.management.openmbean.OpenType;
72  import javax.management.openmbean.SimpleType;
73  import javax.management.openmbean.TabularData;
74  import javax.management.openmbean.TabularDataSupport;
75  import javax.management.openmbean.TabularType;
76  
77  /**
78   *   <p>A converter between Java types and the limited set of classes
79   *   defined by Open MBeans.</p>
80   *
81   *   <p>A Java type is an instance of java.lang.reflect.Type.  For our
82   *   purposes, it is either a Class, such as String.class or int.class;
83   *   or a ParameterizedType, such as List<String> or Map<Integer,
84   *   String[]>.  On J2SE 1.4 and earlier, it can only be a Class.</p>
85   *
86   *   <p>Each Type is associated with an DefaultMXBeanMappingFactory.  The
87   *   DefaultMXBeanMappingFactory defines an OpenType corresponding to the Type, plus a
88   *   Java class corresponding to the OpenType.  For example:</p>
89   *
90   *   <pre>
91   *   Type                     Open class     OpenType
92   *   ----                     ----------     --------
93   *   Integer                Integer        SimpleType.INTEGER
94   *   int                            int            SimpleType.INTEGER
95   *   Integer[]              Integer[]      ArrayType(1, SimpleType.INTEGER)
96   *   int[]                  Integer[]      ArrayType(SimpleType.INTEGER, true)
97   *   String[][]             String[][]     ArrayType(2, SimpleType.STRING)
98   *   List<String>                   String[]       ArrayType(1, SimpleType.STRING)
99   *   ThreadState (an Enum)    String         SimpleType.STRING
100  *   Map<Integer, String[]>   TabularData          TabularType(
101  *                                           CompositeType(
102  *                                             {"key", SimpleType.INTEGER},
103  *                                             {"value",
104  *                                               ArrayType(1,
105  *                                                SimpleType.STRING)}),
106  *                                           indexNames={"key"})
107  *   </pre>
108  *
109  *   <p>Apart from simple types, arrays, and collections, Java types are
110  *   converted through introspection into CompositeType.  The Java type
111  *   must have at least one getter (method such as "int getSize()" or
112  *   "boolean isBig()"), and we must be able to deduce how to
113  *   reconstruct an instance of the Java class from the values of the
114  *   getters using one of various heuristics.</p>
115  *
116  * @since 1.6
117  */
118 public class DefaultMXBeanMappingFactory extends MXBeanMappingFactory {
119     static abstract class NonNullMXBeanMapping extends MXBeanMapping {
120         NonNullMXBeanMapping(Type javaType, OpenType<?> openType) {
121             super(javaType, openType);
122         }
123 
124         @Override
125         public final Object fromOpenValue(Object openValue)
126         throws InvalidObjectException {
127             if (openValue == null)
128                 return null;
129             else
130                 return fromNonNullOpenValue(openValue);
131         }
132 
133         @Override
134         public final Object toOpenValue(Object javaValue) throws OpenDataException {
135             if (javaValue == null)
136                 return null;
137             else
138                 return toNonNullOpenValue(javaValue);
139         }
140 
141         abstract Object fromNonNullOpenValue(Object openValue)
142         throws InvalidObjectException;
143 
144         abstract Object toNonNullOpenValue(Object javaValue)
145         throws OpenDataException;
146 
147         /**
148          * <p>True if and only if this MXBeanMapping's toOpenValue and
149          * fromOpenValue methods are the identity function.</p>
150          */
151         boolean isIdentity() {
152             return false;
153         }
154     }
155 
156     static boolean isIdentity(MXBeanMapping mapping) {
157         return (mapping instanceof NonNullMXBeanMapping &&
158                 ((NonNullMXBeanMapping) mapping).isIdentity());
159     }
160 
161     private static final class Mappings
162         extends WeakHashMap<Type, WeakReference<MXBeanMapping>> {}
163 
164     private static final Mappings mappings = new Mappings();
165 
166     /** Following List simply serves to keep a reference to predefined
167         MXBeanMappings so they don't get garbage collected. */
168     private static final List<MXBeanMapping> permanentMappings = newList();
169 
170     private static synchronized MXBeanMapping getMapping(Type type) {
171         WeakReference<MXBeanMapping> wr = mappings.get(type);
172         return (wr == null) ? null : wr.get();
173     }
174 
175     private static synchronized void putMapping(Type type, MXBeanMapping mapping) {
176         WeakReference<MXBeanMapping> wr =
177             new WeakReference<MXBeanMapping>(mapping);
178         mappings.put(type, wr);
179     }
180 
181     private static synchronized void putPermanentMapping(
182             Type type, MXBeanMapping mapping) {
183         putMapping(type, mapping);
184         permanentMappings.add(mapping);
185     }
186 
187     static {
188         /* Set up the mappings for Java types that map to SimpleType.  */
189 
190         final OpenType<?>[] simpleTypes = {
191             BIGDECIMAL, BIGINTEGER, BOOLEAN, BYTE, CHARACTER, DATE,
192             DOUBLE, FLOAT, INTEGER, LONG, OBJECTNAME, SHORT, STRING,
193             VOID,
194         };
195 
196         for (int i = 0; i < simpleTypes.length; i++) {
197             final OpenType<?> t = simpleTypes[i];
198             Class<?> c;
199             try {
200                 c = Class.forName(t.getClassName(), false,
201                                   ObjectName.class.getClassLoader());
202             } catch (ClassNotFoundException e) {
203                 // the classes that these predefined types declare must exist!
204                 throw new Error(e);
205             }
206             final MXBeanMapping mapping = new IdentityMapping(c, t);
207             putPermanentMapping(c, mapping);
208 
209             if (c.getName().startsWith("java.lang.")) {
210                 try {
211                     final Field typeField = c.getField("TYPE");
212                     final Class<?> primitiveType = (Class<?>) typeField.get(null);
213                     final MXBeanMapping primitiveMapping =
214                         new IdentityMapping(primitiveType, t);
215                     putPermanentMapping(primitiveType, primitiveMapping);
216                     if (primitiveType != void.class) {
217                         final Class<?> primitiveArrayType =
218                             Array.newInstance(primitiveType, 0).getClass();
219                         final OpenType<?> primitiveArrayOpenType =
220                             ArrayType.getPrimitiveArrayType(primitiveArrayType);
221                         final MXBeanMapping primitiveArrayMapping =
222                             new IdentityMapping(primitiveArrayType,
223                                                 primitiveArrayOpenType);
224                         putPermanentMapping(primitiveArrayType,
225                                             primitiveArrayMapping);
226                     }
227                 } catch (NoSuchFieldException e) {
228                     // OK: must not be a primitive wrapper
229                 } catch (IllegalAccessException e) {
230                     // Should not reach here
231                     assert(false);
232                 }
233             }
234         }
235     }
236 
237     /** Get the converter for the given Java type, creating it if necessary. */
238     @Override
239     public synchronized MXBeanMapping mappingForType(Type objType,
240                                                      MXBeanMappingFactory factory)
241             throws OpenDataException {
242         if (inProgress.containsKey(objType)) {
243             throw new OpenDataException(
244                     "Recursive data structure, including " + typeName(objType));
245         }
246 
247         MXBeanMapping mapping;
248 
249         mapping = getMapping(objType);
250         if (mapping != null)
251             return mapping;
252 
253         inProgress.put(objType, objType);
254         try {
255             mapping = makeMapping(objType, factory);
256         } catch (OpenDataException e) {
257             throw openDataException("Cannot convert type: " + typeName(objType), e);
258         } finally {
259             inProgress.remove(objType);
260         }
261 
262         putMapping(objType, mapping);
263         return mapping;
264     }
265 
266     private MXBeanMapping makeMapping(Type objType, MXBeanMappingFactory factory)
267     throws OpenDataException {
268 
269         /* It's not yet worth formalizing these tests by having for example
270            an array of factory classes, each of which says whether it
271            recognizes the Type (Chain of Responsibility pattern).  */
272         if (objType instanceof GenericArrayType) {
273             Type componentType =
274                 ((GenericArrayType) objType).getGenericComponentType();
275             return makeArrayOrCollectionMapping(objType, componentType, factory);
276         } else if (objType instanceof Class<?>) {
277             Class<?> objClass = (Class<?>) objType;
278             if (objClass.isEnum()) {
279                 // Huge hack to avoid compiler warnings here.  The ElementType
280                 // parameter is ignored but allows us to obtain a type variable
281                 // T that matches <T extends Enum<T>>.
282                 return makeEnumMapping((Class<?>) objClass, ElementType.class);
283             } else if (objClass.isArray()) {
284                 Type componentType = objClass.getComponentType();
285                 return makeArrayOrCollectionMapping(objClass, componentType,
286                         factory);
287             } else if (JMX.isMXBeanInterface(objClass)) {
288                 return makeMXBeanRefMapping(objClass);
289             } else {
290                 return makeCompositeMapping(objClass, factory);
291             }
292         } else if (objType instanceof ParameterizedType) {
293             return makeParameterizedTypeMapping((ParameterizedType) objType,
294                                                 factory);
295         } else
296             throw new OpenDataException("Cannot map type: " + objType);
297     }
298 
299     private static <T extends Enum<T>> MXBeanMapping
300             makeEnumMapping(Class<?> enumClass, Class<T> fake) {
301         return new EnumMapping<T>(Util.<Class<T>>cast(enumClass));
302     }
303 
304     /* Make the converter for an array type, or a collection such as
305      * List<String> or Set<Integer>.  We never see one-dimensional
306      * primitive arrays (e.g. int[]) here because they use the identity
307      * converter and are registered as such in the static initializer.
308      */
309     private MXBeanMapping
310         makeArrayOrCollectionMapping(Type collectionType, Type elementType,
311                                      MXBeanMappingFactory factory)
312             throws OpenDataException {
313 
314         final MXBeanMapping elementMapping = factory.mappingForType(elementType, factory);
315         final OpenType<?> elementOpenType = elementMapping.getOpenType();
316         final ArrayType<?> openType = ArrayType.getArrayType(elementOpenType);
317         final Class<?> elementOpenClass = elementMapping.getOpenClass();
318 
319         final Class<?> openArrayClass;
320         final String openArrayClassName;
321         if (elementOpenClass.isArray())
322             openArrayClassName = "[" + elementOpenClass.getName();
323         else
324             openArrayClassName = "[L" + elementOpenClass.getName() + ";";
325         try {
326             openArrayClass = Class.forName(openArrayClassName);
327         } catch (ClassNotFoundException e) {
328             throw openDataException("Cannot obtain array class", e);
329         }
330 
331         if (collectionType instanceof ParameterizedType) {
332             return new CollectionMapping(collectionType,
333                                          openType, openArrayClass,
334                                          elementMapping);
335         } else {
336             if (isIdentity(elementMapping)) {
337                 return new IdentityMapping(collectionType,
338                                            openType);
339             } else {
340                 return new ArrayMapping(collectionType,
341                                           openType,
342                                           openArrayClass,
343                                           elementMapping);
344             }
345         }
346     }
347 
348     private static final String[] keyArray = {"key"};
349     private static final String[] keyValueArray = {"key", "value"};
350 
351     private MXBeanMapping
352         makeTabularMapping(Type objType, boolean sortedMap,
353                            Type keyType, Type valueType,
354                            MXBeanMappingFactory factory)
355             throws OpenDataException {
356 
357         final String objTypeName = typeName(objType);
358         final MXBeanMapping keyMapping = factory.mappingForType(keyType, factory);
359         final MXBeanMapping valueMapping = factory.mappingForType(valueType, factory);
360         final OpenType<?> keyOpenType = keyMapping.getOpenType();
361         final OpenType<?> valueOpenType = valueMapping.getOpenType();
362         final CompositeType rowType =
363             new CompositeType(objTypeName,
364                               objTypeName,
365                               keyValueArray,
366                               keyValueArray,
367                               new OpenType<?>[] {keyOpenType, valueOpenType});
368         final TabularType tabularType =
369             new TabularType(objTypeName, objTypeName, rowType, keyArray);
370         return new TabularMapping(objType, sortedMap, tabularType,
371                                     keyMapping, valueMapping);
372     }
373 
374     /* We know how to translate List<E>, Set<E>, SortedSet<E>,
375        Map<K,V>, SortedMap<K,V>, and that's it.  We don't accept
376        subtypes of those because we wouldn't know how to deserialize
377        them.  We don't accept Queue<E> because it is unlikely people
378        would use that as a parameter or return type in an MBean.  */
379     private MXBeanMapping
380             makeParameterizedTypeMapping(ParameterizedType objType,
381                                          MXBeanMappingFactory factory)
382             throws OpenDataException {
383 
384         final Type rawType = objType.getRawType();
385 
386         if (rawType instanceof Class<?>) {
387             Class<?> c = (Class<?>) rawType;
388             if (c == List.class || c == Set.class || c == SortedSet.class) {
389                 Type[] actuals = objType.getActualTypeArguments();
390                 assert(actuals.length == 1);
391                 if (c == SortedSet.class)
392                     mustBeComparable(c, actuals[0]);
393                 return makeArrayOrCollectionMapping(objType, actuals[0], factory);
394             } else {
395                 boolean sortedMap = (c == SortedMap.class);
396                 if (c == Map.class || sortedMap) {
397                     Type[] actuals = objType.getActualTypeArguments();
398                     assert(actuals.length == 2);
399                     if (sortedMap)
400                         mustBeComparable(c, actuals[0]);
401                     return makeTabularMapping(objType, sortedMap,
402                             actuals[0], actuals[1], factory);
403                 }
404             }
405         }
406         throw new OpenDataException("Cannot convert type: " + objType);
407     }
408 
409     private static MXBeanMapping makeMXBeanRefMapping(Type t)
410             throws OpenDataException {
411         return new MXBeanRefMapping(t);
412     }
413 
414     private MXBeanMapping makeCompositeMapping(Class<?> c,
415                                                MXBeanMappingFactory factory)
416             throws OpenDataException {
417 
418         // For historical reasons GcInfo implements CompositeData but we
419         // shouldn't count its CompositeData.getCompositeType() field as
420         // an item in the computed CompositeType.
421         final boolean gcInfoHack =
422             (c.getName().equals("com.sun.management.GcInfo") &&
423                 c.getClassLoader() == null);
424 
425         final List<Method> methods =
426                 MBeanAnalyzer.eliminateCovariantMethods(Arrays.asList(c.getMethods()));
427         final SortedMap<String,Method> getterMap = newSortedMap();
428 
429         /* Select public methods that look like "T getX()" or "boolean
430            isX()", where T is not void and X is not the empty
431            string.  Exclude "Class getClass()" inherited from Object.  */
432         for (Method method : methods) {
433             final String propertyName = propertyName(method);
434 
435             if (propertyName == null)
436                 continue;
437             if (gcInfoHack && propertyName.equals("CompositeType"))
438                 continue;
439 
440             Method old =
441                 getterMap.put(decapitalize(propertyName),
442                             method);
443             if (old != null) {
444                 final String msg =
445                     "Class " + c.getName() + " has method name clash: " +
446                     old.getName() + ", " + method.getName();
447                 throw new OpenDataException(msg);
448             }
449         }
450 
451         final int nitems = getterMap.size();
452 
453         if (nitems == 0) {
454             throw new OpenDataException("Can't map " + c.getName() +
455                                         " to an open data type");
456         }
457 
458         final Method[] getters = new Method[nitems];
459         final String[] itemNames = new String[nitems];
460         final OpenType<?>[] openTypes = new OpenType<?>[nitems];
461         int i = 0;
462         for (Map.Entry<String,Method> entry : getterMap.entrySet()) {
463             itemNames[i] = entry.getKey();
464             final Method getter = entry.getValue();
465             getters[i] = getter;
466             final Type retType = getter.getGenericReturnType();
467             openTypes[i] = factory.mappingForType(retType, factory).getOpenType();
468             i++;
469         }
470 
471         CompositeType compositeType =
472             new CompositeType(c.getName(),
473                               c.getName(),
474                               itemNames, // field names
475                               itemNames, // field descriptions
476                               openTypes);
477 
478         return new CompositeMapping(c,
479                                     compositeType,
480                                     itemNames,
481                                     getters,
482                                     factory);
483     }
484 
485     /* Converter for classes where the open data is identical to the
486        original data.  This is true for any of the SimpleType types,
487        and for an any-dimension array of those.  It is also true for
488        primitive types as of JMX 1.3, since an int[]
489        can be directly represented by an ArrayType, and an int needs no mapping
490        because reflection takes care of it.  */
491     private static final class IdentityMapping extends NonNullMXBeanMapping {
492         IdentityMapping(Type targetType, OpenType<?> openType) {
493             super(targetType, openType);
494         }
495 
496         boolean isIdentity() {
497             return true;
498         }
499 
500         @Override
501         Object fromNonNullOpenValue(Object openValue)
502         throws InvalidObjectException {
503             return openValue;
504         }
505 
506         @Override
507         Object toNonNullOpenValue(Object javaValue) throws OpenDataException {
508             return javaValue;
509         }
510     }
511 
512     private static final class EnumMapping<T extends Enum<T>>
513             extends NonNullMXBeanMapping {
514 
515         EnumMapping(Class<T> enumClass) {
516             super(enumClass, SimpleType.STRING);
517             this.enumClass = enumClass;
518         }
519 
520         @Override
521         final Object toNonNullOpenValue(Object value) {
522             return ((Enum<?>) value).name();
523         }
524 
525         @Override
526         final T fromNonNullOpenValue(Object value)
527                 throws InvalidObjectException {
528             try {
529                 return Enum.valueOf(enumClass, (String) value);
530             } catch (Exception e) {
531                 throw invalidObjectException("Cannot convert to enum: " +
532                                              value, e);
533             }
534         }
535 
536         private final Class<T> enumClass;
537     }
538 
539     private static final class ArrayMapping extends NonNullMXBeanMapping {
540         ArrayMapping(Type targetType,
541                      ArrayType<?> openArrayType, Class<?> openArrayClass,
542                      MXBeanMapping elementMapping) {
543             super(targetType, openArrayType);
544             this.elementMapping = elementMapping;
545         }
546 
547         @Override
548         final Object toNonNullOpenValue(Object value)
549                 throws OpenDataException {
550             Object[] valueArray = (Object[]) value;
551             final int len = valueArray.length;
552             final Object[] openArray = (Object[])
553                 Array.newInstance(getOpenClass().getComponentType(), len);
554             for (int i = 0; i < len; i++)
555                 openArray[i] = elementMapping.toOpenValue(valueArray[i]);
556             return openArray;
557         }
558 
559         @Override
560         final Object fromNonNullOpenValue(Object openValue)
561                 throws InvalidObjectException {
562             final Object[] openArray = (Object[]) openValue;
563             final Type javaType = getJavaType();
564             final Object[] valueArray;
565             final Type componentType;
566             if (javaType instanceof GenericArrayType) {
567                 componentType =
568                     ((GenericArrayType) javaType).getGenericComponentType();
569             } else if (javaType instanceof Class<?> &&
570                        ((Class<?>) javaType).isArray()) {
571                 componentType = ((Class<?>) javaType).getComponentType();
572             } else {
573                 throw new IllegalArgumentException("Not an array: " +
574                                                    javaType);
575             }
576             valueArray = (Object[]) Array.newInstance((Class<?>) componentType,
577                                                       openArray.length);
578             for (int i = 0; i < openArray.length; i++)
579                 valueArray[i] = elementMapping.fromOpenValue(openArray[i]);
580             return valueArray;
581         }
582 
583         public void checkReconstructible() throws InvalidObjectException {
584             elementMapping.checkReconstructible();
585         }
586 
587         /**
588          * DefaultMXBeanMappingFactory for the elements of this array.  If this is an
589          *          array of arrays, the converter converts the second-level arrays,
590          *          not the deepest elements.
591          */
592         private final MXBeanMapping elementMapping;
593     }
594 
595     private static final class CollectionMapping extends NonNullMXBeanMapping {
596         CollectionMapping(Type targetType,
597                           ArrayType<?> openArrayType,
598                           Class<?> openArrayClass,
599                           MXBeanMapping elementMapping) {
600             super(targetType, openArrayType);
601             this.elementMapping = elementMapping;
602 
603             /* Determine the concrete class to be used when converting
604                back to this Java type.  We convert all Lists to ArrayList
605                and all Sets to TreeSet.  (TreeSet because it is a SortedSet,
606                so works for both Set and SortedSet.)  */
607             Type raw = ((ParameterizedType) targetType).getRawType();
608             Class<?> c = (Class<?>) raw;
609             final Class<?> collC;
610             if (c == List.class)
611                 collC = ArrayList.class;
612             else if (c == Set.class)
613                 collC = HashSet.class;
614             else if (c == SortedSet.class)
615                 collC = TreeSet.class;
616             else { // can't happen
617                 assert(false);
618                 collC = null;
619             }
620             collectionClass = Util.cast(collC);
621         }
622 
623         @Override
624         final Object toNonNullOpenValue(Object value)
625                 throws OpenDataException {
626             final Collection<?> valueCollection = (Collection<?>) value;
627             if (valueCollection instanceof SortedSet<?>) {
628                 Comparator<?> comparator =
629                     ((SortedSet<?>) valueCollection).comparator();
630                 if (comparator != null) {
631                     final String msg =
632                         "Cannot convert SortedSet with non-null comparator: " +
633                         comparator;
634                     throw openDataException(msg, new IllegalArgumentException(msg));
635                 }
636             }
637             final Object[] openArray = (Object[])
638                 Array.newInstance(getOpenClass().getComponentType(),
639                                   valueCollection.size());
640             int i = 0;
641             for (Object o : valueCollection)
642                 openArray[i++] = elementMapping.toOpenValue(o);
643             return openArray;
644         }
645 
646         @Override
647         final Object fromNonNullOpenValue(Object openValue)
648                 throws InvalidObjectException {
649             final Object[] openArray = (Object[]) openValue;
650             final Collection<Object> valueCollection;
651             try {
652                 valueCollection = cast(collectionClass.newInstance());
653             } catch (Exception e) {
654                 throw invalidObjectException("Cannot create collection", e);
655             }
656             for (Object o : openArray) {
657                 Object value = elementMapping.fromOpenValue(o);
658                 if (!valueCollection.add(value)) {
659                     final String msg =
660                         "Could not add " + o + " to " +
661                         collectionClass.getName() +
662                         " (duplicate set element?)";
663                     throw new InvalidObjectException(msg);
664                 }
665             }
666             return valueCollection;
667         }
668 
669         public void checkReconstructible() throws InvalidObjectException {
670             elementMapping.checkReconstructible();
671         }
672 
673         private final Class<? extends Collection<?>> collectionClass;
674         private final MXBeanMapping elementMapping;
675     }
676 
677     private static final class MXBeanRefMapping extends NonNullMXBeanMapping {
678         MXBeanRefMapping(Type intf) {
679             super(intf, SimpleType.OBJECTNAME);
680         }
681 
682         @Override
683         final Object toNonNullOpenValue(Object javaValue)
684                 throws OpenDataException {
685             MXBeanLookup lookup = lookupNotNull(OpenDataException.class);
686             ObjectName name = lookup.mxbeanToObjectName(javaValue);
687             if (name == null)
688                 throw new OpenDataException("No name for object: " + javaValue);
689             return name;
690         }
691 
692         @Override
693         final Object fromNonNullOpenValue(Object openValue)
694                 throws InvalidObjectException {
695             MXBeanLookup lookup = lookupNotNull(InvalidObjectException.class);
696             ObjectName name = (ObjectName) openValue;
697             Object mxbean =
698                 lookup.objectNameToMXBean(name, (Class<?>) getJavaType());
699             if (mxbean == null) {
700                 final String msg =
701                     "No MXBean for name: " + name;
702                 throw new InvalidObjectException(msg);
703             }
704             return mxbean;
705         }
706 
707         private <T extends Exception> MXBeanLookup
708             lookupNotNull(Class<T> excClass)
709                 throws T {
710             MXBeanLookup lookup = MXBeanLookup.getLookup();
711             if (lookup == null) {
712                 final String msg =
713                     "Cannot convert MXBean interface in this context";
714                 T exc;
715                 try {
716                     Constructor<T> con = excClass.getConstructor(String.class);
717                     exc = con.newInstance(msg);
718                 } catch (Exception e) {
719                     throw new RuntimeException(e);
720                 }
721                 throw exc;
722             }
723             return lookup;
724         }
725     }
726 
727     private static final class TabularMapping extends NonNullMXBeanMapping {
728         TabularMapping(Type targetType,
729                        boolean sortedMap,
730                        TabularType tabularType,
731                        MXBeanMapping keyConverter,
732                        MXBeanMapping valueConverter) {
733             super(targetType, tabularType);
734             this.sortedMap = sortedMap;
735             this.keyMapping = keyConverter;
736             this.valueMapping = valueConverter;
737         }
738 
739         @Override
740         final Object toNonNullOpenValue(Object value) throws OpenDataException {
741             final Map<Object, Object> valueMap = cast(value);
742             if (valueMap instanceof SortedMap<?,?>) {
743                 Comparator<?> comparator = ((SortedMap<?,?>) valueMap).comparator();
744                 if (comparator != null) {
745                     final String msg =
746                         "Cannot convert SortedMap with non-null comparator: " +
747                         comparator;
748                     throw openDataException(msg, new IllegalArgumentException(msg));
749                 }
750             }
751             final TabularType tabularType = (TabularType) getOpenType();
752             final TabularData table = new TabularDataSupport(tabularType);
753             final CompositeType rowType = tabularType.getRowType();
754             for (Map.Entry<Object, Object> entry : valueMap.entrySet()) {
755                 final Object openKey = keyMapping.toOpenValue(entry.getKey());
756                 final Object openValue = valueMapping.toOpenValue(entry.getValue());
757                 final CompositeData row;
758                 row =
759                     new CompositeDataSupport(rowType, keyValueArray,
760                                              new Object[] {openKey,
761                                                            openValue});
762                 table.put(row);
763             }
764             return table;
765         }
766 
767         @Override
768         final Object fromNonNullOpenValue(Object openValue)
769                 throws InvalidObjectException {
770             final TabularData table = (TabularData) openValue;
771             final Collection<CompositeData> rows = cast(table.values());
772             final Map<Object, Object> valueMap =
773                 sortedMap ? newSortedMap() : newInsertionOrderMap();
774             for (CompositeData row : rows) {
775                 final Object key =
776                     keyMapping.fromOpenValue(row.get("key"));
777                 final Object value =
778                     valueMapping.fromOpenValue(row.get("value"));
779                 if (valueMap.put(key, value) != null) {
780                     final String msg =
781                         "Duplicate entry in TabularData: key=" + key;
782                     throw new InvalidObjectException(msg);
783                 }
784             }
785             return valueMap;
786         }
787 
788         @Override
789         public void checkReconstructible() throws InvalidObjectException {
790             keyMapping.checkReconstructible();
791             valueMapping.checkReconstructible();
792         }
793 
794         private final boolean sortedMap;
795         private final MXBeanMapping keyMapping;
796         private final MXBeanMapping valueMapping;
797     }
798 
799     private final class CompositeMapping extends NonNullMXBeanMapping {
800         CompositeMapping(Class<?> targetClass,
801                          CompositeType compositeType,
802                          String[] itemNames,
803                          Method[] getters,
804                          MXBeanMappingFactory factory) throws OpenDataException {
805             super(targetClass, compositeType);
806 
807             assert(itemNames.length == getters.length);
808 
809             this.itemNames = itemNames;
810             this.getters = getters;
811             this.getterMappings = new MXBeanMapping[getters.length];
812             for (int i = 0; i < getters.length; i++) {
813                 Type retType = getters[i].getGenericReturnType();
814                 getterMappings[i] = factory.mappingForType(retType, factory);
815             }
816         }
817 
818         @Override
819         final Object toNonNullOpenValue(Object value)
820                 throws OpenDataException {
821             CompositeType ct = (CompositeType) getOpenType();
822             if (value instanceof CompositeDataView)
823                 return ((CompositeDataView) value).toCompositeData(ct);
824             if (value == null)
825                 return null;
826 
827             Object[] values = new Object[getters.length];
828             for (int i = 0; i < getters.length; i++) {
829                 try {
830                     Object got = getters[i].invoke(value, (Object[]) null);
831                     values[i] = getterMappings[i].toOpenValue(got);
832                 } catch (Exception e) {
833                     throw openDataException("Error calling getter for " +
834                                             itemNames[i] + ": " + e, e);
835                 }
836             }
837             return new CompositeDataSupport(ct, itemNames, values);
838         }
839 
840         /** Determine how to convert back from the CompositeData into
841             the original Java type.  For a type that is not reconstructible,
842             this method will fail every time, and will throw the right
843             exception. */
844         private synchronized void makeCompositeBuilder()
845                 throws InvalidObjectException {
846             if (compositeBuilder != null)
847                 return;
848 
849             Class<?> targetClass = (Class<?>) getJavaType();
850             /* In this 2D array, each subarray is a set of builders where
851                there is no point in consulting the ones after the first if
852                the first refuses.  */
853             CompositeBuilder[][] builders = {
854                 {
855                     new CompositeBuilderViaFrom(targetClass, itemNames),
856                 },
857                 {
858                     new CompositeBuilderViaConstructor(targetClass, itemNames),
859                 },
860                 {
861                     new CompositeBuilderCheckGetters(targetClass, itemNames,
862                                                      getterMappings),
863                     new CompositeBuilderViaSetters(targetClass, itemNames),
864                     new CompositeBuilderViaProxy(targetClass, itemNames),
865                 },
866             };
867             CompositeBuilder foundBuilder = null;
868             /* We try to make a meaningful exception message by
869                concatenating each Builder's explanation of why it
870                isn't applicable.  */
871             final StringBuilder whyNots = new StringBuilder();
872             Throwable possibleCause = null;
873         find:
874             for (CompositeBuilder[] relatedBuilders : builders) {
875                 for (int i = 0; i < relatedBuilders.length; i++) {
876                     CompositeBuilder builder = relatedBuilders[i];
877                     String whyNot = builder.applicable(getters);
878                     if (whyNot == null) {
879                         foundBuilder = builder;
880                         break find;
881                     }
882                     Throwable cause = builder.possibleCause();
883                     if (cause != null)
884                         possibleCause = cause;
885                     if (whyNot.length() > 0) {
886                         if (whyNots.length() > 0)
887                             whyNots.append("; ");
888                         whyNots.append(whyNot);
889                         if (i == 0)
890                            break; // skip other builders in this group
891                     }
892                 }
893             }
894             if (foundBuilder == null) {
895                 String msg =
896                     "Do not know how to make a " + targetClass.getName() +
897                     " from a CompositeData: " + whyNots;
898                 if (possibleCause != null)
899                     msg += ". Remaining exceptions show a POSSIBLE cause.";
900                 throw invalidObjectException(msg, possibleCause);
901             }
902             compositeBuilder = foundBuilder;
903         }
904 
905         @Override
906         public void checkReconstructible() throws InvalidObjectException {
907             makeCompositeBuilder();
908         }
909 
910         @Override
911         final Object fromNonNullOpenValue(Object value)
912                 throws InvalidObjectException {
913             makeCompositeBuilder();
914             return compositeBuilder.fromCompositeData((CompositeData) value,
915                                                       itemNames,
916                                                       getterMappings);
917         }
918 
919         private final String[] itemNames;
920         private final Method[] getters;
921         private final MXBeanMapping[] getterMappings;
922         private CompositeBuilder compositeBuilder;
923     }
924 
925     /** Converts from a CompositeData to an instance of the targetClass.  */
926     private static abstract class CompositeBuilder {
927         CompositeBuilder(Class<?> targetClass, String[] itemNames) {
928             this.targetClass = targetClass;
929             this.itemNames = itemNames;
930         }
931 
932         Class<?> getTargetClass() {
933             return targetClass;
934         }
935 
936         String[] getItemNames() {
937             return itemNames;
938         }
939 
940         /** If the subclass is appropriate for targetClass, then the
941             method returns null.  If the subclass is not appropriate,
942             then the method returns an explanation of why not.  If the
943             subclass should be appropriate but there is a problem,
944             then the method throws InvalidObjectException.  */
945         abstract String applicable(Method[] getters)
946                 throws InvalidObjectException;
947 
948         /** If the subclass returns an explanation of why it is not applicable,
949             it can additionally indicate an exception with details.  This is
950             potentially confusing, because the real problem could be that one
951             of the other subclasses is supposed to be applicable but isn't.
952             But the advantage of less information loss probably outweighs the
953             disadvantage of possible confusion.  */
954         Throwable possibleCause() {
955             return null;
956         }
957 
958         abstract Object fromCompositeData(CompositeData cd,
959                                           String[] itemNames,
960                                           MXBeanMapping[] converters)
961                 throws InvalidObjectException;
962 
963         private final Class<?> targetClass;
964         private final String[] itemNames;
965     }
966 
967     /** Builder for when the target class has a method "public static
968         from(CompositeData)".  */
969     private static final class CompositeBuilderViaFrom
970             extends CompositeBuilder {
971 
972         CompositeBuilderViaFrom(Class<?> targetClass, String[] itemNames) {
973             super(targetClass, itemNames);
974         }
975 
976         String applicable(Method[] getters) throws InvalidObjectException {
977             // See if it has a method "T from(CompositeData)"
978             // as is conventional for a CompositeDataView
979             Class<?> targetClass = getTargetClass();
980             try {
981                 Method fromMethod =
982                     targetClass.getMethod("from", CompositeData.class);
983 
984                 if (!Modifier.isStatic(fromMethod.getModifiers())) {
985                     final String msg =
986                         "Method from(CompositeData) is not static";
987                     throw new InvalidObjectException(msg);
988                 }
989 
990                 if (fromMethod.getReturnType() != getTargetClass()) {
991                     final String msg =
992                         "Method from(CompositeData) returns " +
993                         typeName(fromMethod.getReturnType()) +
994                         " not " + typeName(targetClass);
995                     throw new InvalidObjectException(msg);
996                 }
997 
998                 this.fromMethod = fromMethod;
999                 return null; // success!
1000             } catch (InvalidObjectException e) {
1001                 throw e;
1002             } catch (Exception e) {
1003                 // OK: it doesn't have the method
1004                 return "no method from(CompositeData)";
1005             }
1006         }
1007 
1008         final Object fromCompositeData(CompositeData cd,
1009                                        String[] itemNames,
1010                                        MXBeanMapping[] converters)
1011                 throws InvalidObjectException {
1012             try {
1013                 return fromMethod.invoke(null, cd);
1014             } catch (Exception e) {
1015                 final String msg = "Failed to invoke from(CompositeData)";
1016                 throw invalidObjectException(msg, e);
1017             }
1018         }
1019 
1020         private Method fromMethod;
1021     }
1022 
1023     /** This builder never actually returns success.  It simply serves
1024         to check whether the other builders in the same group have any
1025         chance of success.  If any getter in the targetClass returns
1026         a type that we don't know how to reconstruct, then we will
1027         not be able to make a builder, and there is no point in repeating
1028         the error about the problematic getter as many times as there are
1029         candidate builders.  Instead, the "applicable" method will return
1030         an explanatory string, and the other builders will be skipped.
1031         If all the getters are OK, then the "applicable" method will return
1032         an empty string and the other builders will be tried.  */
1033     private static class CompositeBuilderCheckGetters extends CompositeBuilder {
1034         CompositeBuilderCheckGetters(Class<?> targetClass, String[] itemNames,
1035                                      MXBeanMapping[] getterConverters) {
1036             super(targetClass, itemNames);
1037             this.getterConverters = getterConverters;
1038         }
1039 
1040         String applicable(Method[] getters) {
1041             for (int i = 0; i < getters.length; i++) {
1042                 try {
1043                     getterConverters[i].checkReconstructible();
1044                 } catch (InvalidObjectException e) {
1045                     possibleCause = e;
1046                     return "method " + getters[i].getName() + " returns type " +
1047                         "that cannot be mapped back from OpenData";
1048                 }
1049             }
1050             return "";
1051         }
1052 
1053         @Override
1054         Throwable possibleCause() {
1055             return possibleCause;
1056         }
1057 
1058         final Object fromCompositeData(CompositeData cd,
1059                                        String[] itemNames,
1060                                        MXBeanMapping[] converters) {
1061             throw new Error();
1062         }
1063 
1064         private final MXBeanMapping[] getterConverters;
1065         private Throwable possibleCause;
1066     }
1067 
1068     /** Builder for when the target class has a setter for every getter. */
1069     private static class CompositeBuilderViaSetters extends CompositeBuilder {
1070 
1071         CompositeBuilderViaSetters(Class<?> targetClass, String[] itemNames) {
1072             super(targetClass, itemNames);
1073         }
1074 
1075         String applicable(Method[] getters) {
1076             try {
1077                 Constructor<?> c = getTargetClass().getConstructor();
1078             } catch (Exception e) {
1079                 return "does not have a public no-arg constructor";
1080             }
1081 
1082             Method[] setters = new Method[getters.length];
1083             for (int i = 0; i < getters.length; i++) {
1084                 Method getter = getters[i];
1085                 Class<?> returnType = getter.getReturnType();
1086                 String name = propertyName(getter);
1087                 String setterName = "set" + name;
1088                 Method setter;
1089                 try {
1090                     setter = getTargetClass().getMethod(setterName, returnType);
1091                     if (setter.getReturnType() != void.class)
1092                         throw new Exception();
1093                 } catch (Exception e) {
1094                     return "not all getters have corresponding setters " +
1095                            "(" + getter + ")";
1096                 }
1097                 setters[i] = setter;
1098             }
1099             this.setters = setters;
1100             return null;
1101         }
1102 
1103         Object fromCompositeData(CompositeData cd,
1104                                  String[] itemNames,
1105                                  MXBeanMapping[] converters)
1106                 throws InvalidObjectException {
1107             Object o;
1108             try {
1109                 o = getTargetClass().newInstance();
1110                 for (int i = 0; i < itemNames.length; i++) {
1111                     if (cd.containsKey(itemNames[i])) {
1112                         Object openItem = cd.get(itemNames[i]);
1113                         Object javaItem =
1114                             converters[i].fromOpenValue(openItem);
1115                         setters[i].invoke(o, javaItem);
1116                     }
1117                 }
1118             } catch (Exception e) {
1119                 throw invalidObjectException(e);
1120             }
1121             return o;
1122         }
1123 
1124         private Method[] setters;
1125     }
1126 
1127     /** Builder for when the target class has a constructor that is
1128         annotated with @ConstructorProperties so we can see the correspondence
1129         to getters.  */
1130     private static final class CompositeBuilderViaConstructor
1131             extends CompositeBuilder {
1132 
1133         CompositeBuilderViaConstructor(Class<?> targetClass, String[] itemNames) {
1134             super(targetClass, itemNames);
1135         }
1136 
1137         String applicable(Method[] getters) throws InvalidObjectException {
1138 
1139             final Class<ConstructorProperties> propertyNamesClass = ConstructorProperties.class;
1140 
1141             Class<?> targetClass = getTargetClass();
1142             Constructor<?>[] constrs = targetClass.getConstructors();
1143 
1144             // Applicable if and only if there are any annotated constructors
1145             List<Constructor<?>> annotatedConstrList = newList();
1146             for (Constructor<?> constr : constrs) {
1147                 if (Modifier.isPublic(constr.getModifiers())
1148                         && constr.getAnnotation(propertyNamesClass) != null)
1149                     annotatedConstrList.add(constr);
1150             }
1151 
1152             if (annotatedConstrList.isEmpty())
1153                 return "no constructor has @ConstructorProperties annotation";
1154 
1155             annotatedConstructors = newList();
1156 
1157             // Now check that all the annotated constructors are valid
1158             // and throw an exception if not.
1159 
1160             // First link the itemNames to their getter indexes.
1161             Map<String, Integer> getterMap = newMap();
1162             String[] itemNames = getItemNames();
1163             for (int i = 0; i < itemNames.length; i++)
1164                 getterMap.put(itemNames[i], i);
1165 
1166             // Run through the constructors making the checks in the spec.
1167             // For each constructor, remember the correspondence between its
1168             // parameters and the items.  The int[] for a constructor says
1169             // what parameter index should get what item.  For example,
1170             // if element 0 is 2 then that means that item 0 in the
1171             // CompositeData goes to parameter 2 of the constructor.  If an
1172             // element is -1, that item isn't given to the constructor.
1173             // Also remember the set of properties in that constructor
1174             // so we can test unambiguity.
1175             Set<BitSet> getterIndexSets = newSet();
1176             for (Constructor<?> constr : annotatedConstrList) {
1177                 String[] propertyNames =
1178                     constr.getAnnotation(propertyNamesClass).value();
1179 
1180                 Type[] paramTypes = constr.getGenericParameterTypes();
1181                 if (paramTypes.length != propertyNames.length) {
1182                     final String msg =
1183                         "Number of constructor params does not match " +
1184                         "@ConstructorProperties annotation: " + constr;
1185                     throw new InvalidObjectException(msg);
1186                 }
1187 
1188                 int[] paramIndexes = new int[getters.length];
1189                 for (int i = 0; i < getters.length; i++)
1190                     paramIndexes[i] = -1;
1191                 BitSet present = new BitSet();
1192 
1193                 for (int i = 0; i < propertyNames.length; i++) {
1194                     String propertyName = propertyNames[i];
1195                     if (!getterMap.containsKey(propertyName)) {
1196                         String msg =
1197                             "@ConstructorProperties includes name " + propertyName +
1198                             " which does not correspond to a property";
1199                         for (String getterName : getterMap.keySet()) {
1200                             if (getterName.equalsIgnoreCase(propertyName)) {
1201                                 msg += " (differs only in case from property " +
1202                                         getterName + ")";
1203                             }
1204                         }
1205                         msg += ": " + constr;
1206                         throw new InvalidObjectException(msg);
1207                     }
1208                     int getterIndex = getterMap.get(propertyName);
1209                     paramIndexes[getterIndex] = i;
1210                     if (present.get(getterIndex)) {
1211                         final String msg =
1212                             "@ConstructorProperties contains property " +
1213                             propertyName + " more than once: " + constr;
1214                         throw new InvalidObjectException(msg);
1215                     }
1216                     present.set(getterIndex);
1217                     Method getter = getters[getterIndex];
1218                     Type propertyType = getter.getGenericReturnType();
1219                     if (!propertyType.equals(paramTypes[i])) {
1220                         final String msg =
1221                             "@ConstructorProperties gives property " + propertyName +
1222                             " of type " + propertyType + " for parameter " +
1223                             " of type " + paramTypes[i] + ": " + constr;
1224                         throw new InvalidObjectException(msg);
1225                     }
1226                 }
1227 
1228                 if (!getterIndexSets.add(present)) {
1229                     final String msg =
1230                         "More than one constructor has a @ConstructorProperties " +
1231                         "annotation with this set of names: " +
1232                         Arrays.toString(propertyNames);
1233                     throw new InvalidObjectException(msg);
1234                 }
1235 
1236                 Constr c = new Constr(constr, paramIndexes, present);
1237                 annotatedConstructors.add(c);
1238             }
1239 
1240             /* Check that no possible set of items could lead to an ambiguous
1241              * choice of constructor (spec requires this check).  For any
1242              * pair of constructors, their union would be the minimal
1243              * ambiguous set.  If this set itself corresponds to a constructor,
1244              * there is no ambiguity for that pair.  In the usual case, one
1245              * of the constructors is a superset of the other so the union is
1246              * just the bigger constuctor.
1247              *
1248              * The algorithm here is quadratic in the number of constructors
1249              * with a @ConstructorProperties annotation.  Typically this corresponds
1250              * to the number of versions of the class there have been.  Ten
1251              * would already be a large number, so although it's probably
1252              * possible to have an O(n lg n) algorithm it wouldn't be
1253              * worth the complexity.
1254              */
1255             for (BitSet a : getterIndexSets) {
1256                 boolean seen = false;
1257                 for (BitSet b : getterIndexSets) {
1258                     if (a == b)
1259                         seen = true;
1260                     else if (seen) {
1261                         BitSet u = new BitSet();
1262                         u.or(a); u.or(b);
1263                         if (!getterIndexSets.contains(u)) {
1264                             Set<String> names = new TreeSet<String>();
1265                             for (int i = u.nextSetBit(0); i >= 0;
1266                                  i = u.nextSetBit(i+1))
1267                                 names.add(itemNames[i]);
1268                             final String msg =
1269                                 "Constructors with @ConstructorProperties annotation " +
1270                                 " would be ambiguous for these items: " +
1271                                 names;
1272                             throw new InvalidObjectException(msg);
1273                         }
1274                     }
1275                 }
1276             }
1277 
1278             return null; // success!
1279         }
1280 
1281         final Object fromCompositeData(CompositeData cd,
1282                                        String[] itemNames,
1283                                        MXBeanMapping[] mappings)
1284                 throws InvalidObjectException {
1285             // The CompositeData might come from an earlier version where
1286             // not all the items were present.  We look for a constructor
1287             // that accepts just the items that are present.  Because of
1288             // the ambiguity check in applicable(), we know there must be
1289             // at most one maximally applicable constructor.
1290             CompositeType ct = cd.getCompositeType();
1291             BitSet present = new BitSet();
1292             for (int i = 0; i < itemNames.length; i++) {
1293                 if (ct.getType(itemNames[i]) != null)
1294                     present.set(i);
1295             }
1296 
1297             Constr max = null;
1298             for (Constr constr : annotatedConstructors) {
1299                 if (subset(constr.presentParams, present) &&
1300                         (max == null ||
1301                          subset(max.presentParams, constr.presentParams)))
1302                     max = constr;
1303             }
1304 
1305             if (max == null) {
1306                 final String msg =
1307                     "No constructor has a @ConstructorProperties for this set of " +
1308                     "items: " + ct.keySet();
1309                 throw new InvalidObjectException(msg);
1310             }
1311 
1312             Object[] params = new Object[max.presentParams.cardinality()];
1313             for (int i = 0; i < itemNames.length; i++) {
1314                 if (!max.presentParams.get(i))
1315                     continue;
1316                 Object openItem = cd.get(itemNames[i]);
1317                 Object javaItem = mappings[i].fromOpenValue(openItem);
1318                 int index = max.paramIndexes[i];
1319                 if (index >= 0)
1320                     params[index] = javaItem;
1321             }
1322 
1323             try {
1324                 return max.constructor.newInstance(params);
1325             } catch (Exception e) {
1326                 final String msg =
1327                     "Exception constructing " + getTargetClass().getName();
1328                 throw invalidObjectException(msg, e);
1329             }
1330         }
1331 
1332         private static boolean subset(BitSet sub, BitSet sup) {
1333             BitSet subcopy = (BitSet) sub.clone();
1334             subcopy.andNot(sup);
1335             return subcopy.isEmpty();
1336         }
1337 
1338         private static class Constr {
1339             final Constructor<?> constructor;
1340             final int[] paramIndexes;
1341             final BitSet presentParams;
1342             Constr(Constructor<?> constructor, int[] paramIndexes,
1343                    BitSet presentParams) {
1344                 this.constructor = constructor;
1345                 this.paramIndexes = paramIndexes;
1346                 this.presentParams = presentParams;
1347             }
1348         }
1349 
1350         private List<Constr> annotatedConstructors;
1351     }
1352 
1353     /** Builder for when the target class is an interface and contains
1354         no methods other than getters.  Then we can make an instance
1355         using a dynamic proxy that forwards the getters to the source
1356         CompositeData.  */
1357     private static final class CompositeBuilderViaProxy
1358             extends CompositeBuilder {
1359 
1360         CompositeBuilderViaProxy(Class<?> targetClass, String[] itemNames) {
1361             super(targetClass, itemNames);
1362         }
1363 
1364         String applicable(Method[] getters) {
1365             Class<?> targetClass = getTargetClass();
1366             if (!targetClass.isInterface())
1367                 return "not an interface";
1368             Set<Method> methods =
1369                 newSet(Arrays.asList(targetClass.getMethods()));
1370             methods.removeAll(Arrays.asList(getters));
1371             /* If the interface has any methods left over, they better be
1372              * public methods that are already present in java.lang.Object.
1373              */
1374             String bad = null;
1375             for (Method m : methods) {
1376                 String mname = m.getName();
1377                 Class<?>[] mparams = m.getParameterTypes();
1378                 try {
1379                     Method om = Object.class.getMethod(mname, mparams);
1380                     if (!Modifier.isPublic(om.getModifiers()))
1381                         bad = mname;
1382                 } catch (NoSuchMethodException e) {
1383                     bad = mname;
1384                 }
1385                 /* We don't catch SecurityException since it shouldn't
1386                  * happen for a method in Object and if it does we would
1387                  * like to know about it rather than mysteriously complaining.
1388                  */
1389             }
1390             if (bad != null)
1391                 return "contains methods other than getters (" + bad + ")";
1392             return null; // success!
1393         }
1394 
1395         final Object fromCompositeData(CompositeData cd,
1396                                        String[] itemNames,
1397                                        MXBeanMapping[] converters) {
1398             final Class<?> targetClass = getTargetClass();
1399             return
1400                 Proxy.newProxyInstance(targetClass.getClassLoader(),
1401                                        new Class<?>[] {targetClass},
1402                                        new CompositeDataInvocationHandler(cd));
1403         }
1404     }
1405 
1406     static InvalidObjectException invalidObjectException(String msg,
1407                                                          Throwable cause) {
1408         return EnvHelp.initCause(new InvalidObjectException(msg), cause);
1409     }
1410 
1411     static InvalidObjectException invalidObjectException(Throwable cause) {
1412         return invalidObjectException(cause.getMessage(), cause);
1413     }
1414 
1415     static OpenDataException openDataException(String msg, Throwable cause) {
1416         return EnvHelp.initCause(new OpenDataException(msg), cause);
1417     }
1418 
1419     static OpenDataException openDataException(Throwable cause) {
1420         return openDataException(cause.getMessage(), cause);
1421     }
1422 
1423     static void mustBeComparable(Class<?> collection, Type element)
1424             throws OpenDataException {
1425         if (!(element instanceof Class<?>)
1426             || !Comparable.class.isAssignableFrom((Class<?>) element)) {
1427             final String msg =
1428                 "Parameter class " + element + " of " +
1429                 collection.getName() + " does not implement " +
1430                 Comparable.class.getName();
1431             throw new OpenDataException(msg);
1432         }
1433     }
1434 
1435     /**
1436      * Utility method to take a string and convert it to normal Java variable
1437      * name capitalization.  This normally means converting the first
1438      * character from upper case to lower case, but in the (unusual) special
1439      * case when there is more than one character and both the first and
1440      * second characters are upper case, we leave it alone.
1441      * <p>
1442      * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
1443      * as "URL".
1444      *
1445      * @param  name The string to be decapitalized.
1446      * @return  The decapitalized version of the string.
1447      */
1448     public static String decapitalize(String name) {
1449         if (name == null || name.length() == 0) {
1450             return name;
1451         }
1452         int offset1 = Character.offsetByCodePoints(name, 0, 1);
1453         // Should be name.offsetByCodePoints but 6242664 makes this fail
1454         if (offset1 < name.length() &&
1455                 Character.isUpperCase(name.codePointAt(offset1)))
1456             return name;
1457         return name.substring(0, offset1).toLowerCase() +
1458                name.substring(offset1);
1459     }
1460 
1461     /**
1462      * Reverse operation for java.beans.Introspector.decapitalize.  For any s,
1463      * capitalize(decapitalize(s)).equals(s).  The reverse is not true:
1464      * e.g. capitalize("uRL") produces "URL" which is unchanged by
1465      * decapitalize.
1466      */
1467     static String capitalize(String name) {
1468         if (name == null || name.length() == 0)
1469             return name;
1470         int offset1 = name.offsetByCodePoints(0, 1);
1471         return name.substring(0, offset1).toUpperCase() +
1472                name.substring(offset1);
1473     }
1474 
1475     public static String propertyName(Method m) {
1476         String rest = null;
1477         String name = m.getName();
1478         if (name.startsWith("get"))
1479             rest = name.substring(3);
1480         else if (name.startsWith("is") && m.getReturnType() == boolean.class)
1481             rest = name.substring(2);
1482         if (rest == null || rest.length() == 0
1483             || m.getParameterTypes().length > 0
1484             || m.getReturnType() == void.class
1485             || name.equals("getClass"))
1486             return null;
1487         return rest;
1488     }
1489 
1490     private final static Map<Type, Type> inProgress = newIdentityHashMap();
1491     // really an IdentityHashSet but that doesn't exist
1492 }